#!/usr/bin/env python3

# Copyright (c) 2005-2014 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


import os
import io
import gzip
import time
import mrget
import pathlib
import subprocess
import robust_layer
import lxml.html
import urllib.request
from ..._plugin import KernelPlugin


class Plugin(KernelPlugin):

    __verFile = "linux.versions"

    def sync(self, context):
        root = util.fetchAndParseHtmlPage(self.description, "mirror://kernel-org")
        td = root.xpath(".//td[text()='%s:']" % ("stable"))[0]
        td = td.getnext()
        while len(td) > 0:
            td = td[0]

        with context.distfiles_open_for_write(self.__verFile) as f:
            # f.write(td.text + "\n")
            f.write("5.18.15\n")

    def list_versions(self, context):
        return util.readVersionsFile(context, self.__verFile)

    def list_distfiles(self, context):
        ret = []
        for version in context.versions:
            localFn, localSignFn = self.__localFnTuple(version)
            ret.append(context.distfiles_fn(localFn))
            ret.append(context.distfiles_fn(localSignFn))
        return ret

    def fetch(self, context):
        for version in context.versions:
            # local filenames
            localFn, localSignFn = self.__localFnTuple(version)

            # get remote urls
            # won't access internet if local file exists
            remoteUrl = None
            remoteSignUrl = None
            while context.distfiles_find(localFn) is None or context.distfiles_find(localSignFn) is None:
                baseUrl = "mirror://kernel-org-pub/linux/kernel"

                # try mirror file structure 1: all files placed under / (a simple structure suitable for local mirrors)
                ret = util.getTargetUrl(baseUrl, (localFn, localSignFn))
                if ret is not None:
                    remoteUrl, remoteSignUrl = ret
                    break

                # try mirror file structure 2: /{v3.x,v4.x,...}/* (an overly complicated structure used by official kernel mirrors)
                subdir = None
                for i in range(3, 9):
                    if version.startswith(str(i)):
                        subdir = "v%d.x" % (i)
                assert subdir is not None

                ret = util.getTargetUrl(baseUrl, (os.path.join(subdir, localFn), os.path.join(subdir, localSignFn)))
                if ret is not None:
                    remoteUrl, remoteSignUrl = ret
                    break

                # invalid
                raise self.FetchError("can not find remote file for version %s" % (version))

            # download from remote urls
            if remoteUrl is not None:
                context.distfiles_download(remoteUrl, fn=localFn)
                context.distfiles_download(remoteSignUrl, fn=localSignFn)

    def unpack(self, context):
        localFn, localSignFn = self.__localFnTuple(context.version)
        subprocess.check_call(["tar", "-x", "-J", "--strip-components=1", "-f", context.distfiles_get(localFn)])

    def install(self, context):
        makeOpts = context.get_build_variable("MAKEOPTS")
        kernelImageFile = os.path.join("arch", context.get_boot_entry().spec.arch.value, "boot", "bzImage")
        kernelConfigFile = ".config"

        # may change the .config file further
        # we'd better use make with shell=True because make is too old, for example it may not use os working directory but reads bash $PWD
        subprocess.check_call("make %s olddefconfig" % (makeOpts), shell=True)

        subprocess.check_call('make CFLAGS="-Wno-error" %s' % (makeOpts), shell=True)

        subprocess.check_call(["install", "-D", "-m0644", "-o", "0", "-g", "0", kernelImageFile, context.get_boot_entry().kernel_filepath])
        subprocess.check_call(["install", "-D", "-m0644", "-o", "0", "-g", "0", kernelConfigFile, context.get_boot_entry().kernel_config_filepath])
        #subprocess.check_call(["install", "-D", "-m0644", "-o", "0", "-g", "0", "System.map", context.get_boot_entry().system_map_filepath])

        subprocess.check_call(["rm", "-rf", context.get_boot_entry().kernel_modules_dirpath])
        subprocess.check_call('make %s modules_install' % (makeOpts), shell=True)

    def cleanup(self, context):
        subprocess.check_call(["rm", "-f", os.path.join(context.get_boot_entry().kernel_modules_dirpath, "build")])        # remove symlink
        subprocess.check_call(["rm", "-f", os.path.join(context.get_boot_entry().kernel_modules_dirpath, "source")])       # remove symlink

    def __localFnTuple(self, version):
        return ("linux-%s.tar.xz" % (version), "linux-%s.tar.sign" % (version))


class util:

    @staticmethod
    def readVersionsFile(context, fn):
        ret = []
        for line in pathlib.Path(context.distfiles_get(fn)).read_text().split("\n"):
            line = line.strip()
            if line == "" or line.startswith("#"):
                continue
            ret.append(line)
        return ret

    @staticmethod
    def fetchAndParseHtmlPage(description, url):
        if url.startswith("mirror://"):
            url = mrget.target_urls(url)[0]

        while True:
            try:
                with urllib.request.urlopen(url, timeout=robust_layer.TIMEOUT) as resp:
                    if resp.info().get('Content-Encoding') is None:
                        fakef = resp
                    elif resp.info().get('Content-Encoding') == 'gzip':
                        fakef = io.BytesIO(resp.read())
                        fakef = gzip.GzipFile(fileobj=fakef)
                    else:
                        assert False
                    return lxml.html.parse(fakef)
            except OSError as e:
                print("%s: Failed to acces %s, %s" % (description, url, e))
                time.sleep(robust_layer.RETRY_WAIT)

    @staticmethod
    def cmdCallTestSuccess(cmd, *kargs):
        ret = subprocess.run([cmd] + list(kargs), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
        return (ret.returncode == 0)

    @staticmethod
    def getTargetUrl(baseUrl, urlTuple):
        if baseUrl.startswith("mirror://"):
            baseUrlList = mrget.target_urls(baseUrl)
        else:
            baseUrlList = [baseUrl]

        for baseUrl in baseUrlList:
            ret = []
            for url in urlTuple:
                url = os.path.join(baseUrl, url)
                if not util.cmdCallTestSuccess("wget", "--spider", url):
                    break
                ret.append(url)
            if len(ret) == len(urlTuple):
                return tuple(ret)

        return None
